package top.wenzz.manage.system.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
import org.springframework.transaction.annotation.Transactional;
import top.wenzz.commons.utils.SpringSecurityUtils;
import top.wenzz.commons.utils.StringUtils;
import top.wenzz.manage.system.manage.service.SysPermissionService;
import top.wenzz.manage.system.manage.service.SysUserService;
import top.wenzz.commons.utils.JsonUtils;
import top.wenzz.commons.vo.RetMsg;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * Spring Security配置
 * Created by 钟廷员 on 2016/12/5.
 */
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String LOGIN_URL = "/login";

    @Autowired
    private SysPermissionService permissionService;

    @Autowired
    private SysUserService sysUserService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // auth.inMemoryAuthentication().withUser("user").password("pw").roles("USER");
        auth.authenticationProvider(loginAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry urlRegistry =
                http.authorizeRequests().antMatchers("/resources/**", "/login*", "/403*").permitAll();

        // 注册资源
        permissionService.initSpringSecurityPermission(urlRegistry);

        ((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl) urlRegistry.antMatchers("/**")).authenticated();

        // 登陆
        http.formLogin()
                .loginPage(LOGIN_URL)
                .successHandler(loginSuccess())
                .failureHandler(loginFailure())
                .permitAll();

        // 推出登陆
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .and()
                .httpBasic();

        http.exceptionHandling().accessDeniedHandler(accessDenied());

        // 必须配置，才能获取登录用户信息
        http.sessionManagement()
                .maximumSessions(Integer.MAX_VALUE)
                .sessionRegistry(sessionRegistry())
                .expiredUrl(LOGIN_URL);

        super.configure(http);
    }

    @Bean
    public AuthenticationSuccessHandler loginSuccess() {
        return (request, response, authentication) -> {
            response.setContentType("application/json;charset=UTF-8");
            Map<String, Object> retData = SpringSecurityUtils.getWebUserData();
            retData.put("id", ((CsrfToken) request.getAttribute("_csrf")).getToken());
            response.getWriter().write(JsonUtils.formatObjectToJson(RetMsg.success(retData)));
        };
    }

    @Bean
    public AuthenticationFailureHandler loginFailure() {
        return (request, response, authentication) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(JsonUtils.formatObjectToJson(RetMsg.error(authentication.getMessage())));
        };
    }

    @Bean
    public AccessDeniedHandler accessDenied() {
        return (request, response, accessDeniedException) -> {
            if (!response.isCommitted()) {
                if(StringUtils.isNotEmpty(request.getContentType()) &&
                        MediaType.valueOf(request.getContentType()).includes(MediaType.TEXT_HTML)) {
                    response.sendRedirect("/403");
                } else {
                    response.sendError(HttpServletResponse.SC_FORBIDDEN,
                            accessDeniedException.getMessage());
                }
            }

        };
    }

    private SpringSessionBackedSessionRegistry sessionRegistry() {
        return sessionRegistry(null);
    }

    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(RedisOperationsSessionRepository sessionRepository) {
        return new SpringSessionBackedSessionRegistry((FindByIndexNameSessionRepository) sessionRepository);
    }

    @Bean
    public AbstractUserDetailsAuthenticationProvider loginAuthenticationProvider() {
        return new AbstractUserDetailsAuthenticationProvider() {

            @Override
            protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
                // 密码校验放在sysUserService.login方法上校验
            }

            @Override
            @Transactional
            protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
                if (authentication.getCredentials() == null)
                    throw new BadCredentialsException("输入的密码为空");
                UserDetails userDetails = sysUserService.login(username, authentication.getCredentials().toString());
                if(userDetails == null)
                    throw new BadCredentialsException("用户名或密码错误，登陆失败");
                return userDetails;
            }
        };
    }

    public static void main(String[] args) {
        MediaType type = MediaType.valueOf("text/html;charset=UTF-8");
        System.out.println(type);
        System.out.println(type.includes(MediaType.TEXT_HTML));
    }
}
